const express = require("express");
const logger = require("morgan");
const cors = require("cors");
const fs = require("fs-extra");
const fsTarget = require("node:fs");
const path = require("path");

const PUBLIC_DIR = path.resolve(__dirname, "public");
const TEMP_DIR = path.resolve(__dirname, "temp");
//存放上传并合并好的文件
fs.ensureDirSync(PUBLIC_DIR);
//存放分片的文件
fs.ensureDirSync(TEMP_DIR);

const app = express();
app.use(logger("dev"));
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static(path.resolve(__dirname, "public")));

class ICommonResponse {
  constructor(code, success, data, message) {
    this.code = code;
    this.success = success;
    this.data = data || null;
    this.message = message || null;
  }

  static success(data) {
    return new ICommonResponse(200, true, data);
  }
}

// 实现分片 上传
app.post("/upload/section/:baseDir/:filename", async (req, res, next) => {
  //通过路径参数获取文件名
  const { baseDir, filename } = req.params;

  //创建用户保存此文件的分片的目录
  const chunkDir = path.resolve(TEMP_DIR, baseDir);
  //分片的文件路径
  const chunkFilePath = path.resolve(chunkDir, filename);
  //先确定分片目录存在
  await fs.ensureDir(chunkDir);

  //创建此文件的可写流 ,可以指定写入的起始位置
  const ws = fsTarget.createWriteStream(chunkFilePath);
  req.pipe(ws);

  ws.on("finish", function () {
    ws.close();
    res.json(ICommonResponse.success(true));
  });
});

// 此处是 文件合并 的操作
app.get("/upload/merge/:baseDir/:filename", async (req, res, next) => {
  // 通过路径参数获取文件名
  const { filename, baseDir } = req.params;

  try {
    await mergeChunks(baseDir, filename);
    res.json(ICommonResponse.success(true));
  } catch (error) {
    next(error);
  }
});

// 验证文件是否存在，为了实现秒传的功能
app.get("/upload/verify/:filename", async (req, res, next) => {
  const { filename } = req.params;
  //先获取文件在服务器的路径
  const filePath = path.resolve(PUBLIC_DIR, filename);
  //判断是文件在服务器端是否存在
  const isExist = await fs.pathExists(filePath);

  return res.json(ICommonResponse.success(isExist));
});

// 根据 filename 拿到文件列表
app.get("/upload/list/:filename", async (req, res, next) => {
  const { filename } = req.params;
  //先获取文件在服务器的路径
  const filePath = path.resolve(PUBLIC_DIR, filename);
  //判断是文件在服务器端是否存在
  const isExist = await fs.pathExists(filePath);

  // 如果文件不存在的话
  if (!isExist) return res.json(ICommonResponse.success(null));

  let fileSize = 0;
  // 尝试读取目录
  const dirs = fsTarget.readdirSync(filePath);

  // 遍历目录 拿到字节数
  for (const dirName of dirs) {
    const newFilePath = path.join(filePath, dirName);
    fileSize += fsTarget.statSync(newFilePath).size;
  }
  return res.json(ICommonResponse.success([dirs.length, fileSize]));
});

/**
 * merge 分片文件
 *
 * @author lihh
 * @param baseDir 基础目录
 * @param filename 文件名称
 * @return {Promise<void>}
 */
function mergeChunks(baseDir, filename) {
  return new Promise((resolve) => {
    // 将要合并的文件
    const mergedFilePath = path.resolve(PUBLIC_DIR, filename);
    // 临时文件目录
    const chunkDir = path.resolve(TEMP_DIR, baseDir);
    // 临时文件
    const chunkFiles = fsTarget.readdirSync(chunkDir);
    //对分片按索引进行升序排列
    chunkFiles.sort(
      (a, b) => Number(a.split("-")[1]) - Number(b.split("-")[1]),
    );

    // 写文件
    const ws = fsTarget.createWriteStream(mergedFilePath);
    chunkFiles.forEach((fileName) => {
      const readStream = fsTarget.createReadStream(
        path.join(chunkDir, fileName),
      );
      readStream.pipe(ws, { end: false });

      readStream.on("end", function () {
        if (chunkFiles.indexOf(fileName) === chunkFiles.length - 1) {
          ws.end();
        }
      });
    });

    ws.on("finish", function () {
      //删除分片的文件和文件夹
      fsTarget.rmdirSync(chunkDir, { recursive: true });
      resolve();
    });
  });
}

function pipeStream(rs, ws) {
  return new Promise((resolve, reject) => {
    //把可读流中的数据写入可写流中
    rs.pipe(ws).on("finish", resolve).on("error", reject);
  });
}

app.listen(9334, () => console.log("Sever started on port 9334"));
